package com.concurrent.sample.business.manager.impl;

import com.concurrent.sample.business.consts.BusinessRedisCacheConsts;
import com.concurrent.sample.business.dto.CashFlowMessageDTO;
import com.concurrent.sample.business.entity.CashFlowEntity;
import com.concurrent.sample.business.manager.CashFlowManagerService;
import com.concurrent.sample.business.service.CashFlowService;
import com.concurrent.sample.common.enums.ExpressionTypeEnum;
import com.concurrent.sample.common.lock.DistributedLock;
import com.concurrent.sample.common.util.BigDecimalUtil;
import java.math.BigDecimal;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class CashFlowManagerServiceImpl implements CashFlowManagerService {

  private final CashFlowService cashFlowService;

  private final RedissonClient redissonClient;

  @DistributedLock(defaultPrefix = BusinessRedisCacheConsts.CUSTOMER_LOCK_PREFIX_KEY, expression = "#context.customerNo",
      expressionType = ExpressionTypeEnum.SPEL_EXPRESSION)
  @Override
  public CashFlowEntity saveByLock(CashFlowMessageDTO param) {
    return cashFlowService.saveWithIncremental(param);
  }

  @Override
  public CashFlowEntity loadMemoryByCustomer(String customerNo) {
    RBucket<CashFlowEntity> customerCashRBucket = getCustomerCashRBucket(customerNo);
    CashFlowEntity cashFlow = customerCashRBucket.get();
    // 双重锁初始化处理
    if(Objects.isNull(cashFlow)){
      log.info("load memory by db, customerNo = {}", customerNo);
      cashFlow = cashFlowService.findByCustomerNo(customerNo);
      if(Objects.isNull(cashFlow)){
        // 没有的话则新增一条资金
        cashFlow = cashFlowService.save(new CashFlowEntity()
            .setSerialNo(0L)
            .setCustomerNo(customerNo)
            .setAmount(BigDecimal.ZERO)
            .setTotalAmount(BigDecimal.ZERO));
      }
      customerCashRBucket.set(cashFlow);
    }
    return cashFlow;
  }

  @Override
  public void saveMemoryByCustomer(CashFlowEntity cashFlowEntity) {
    RBucket<CashFlowEntity> customerCashRBucket = getCustomerCashRBucket(
        cashFlowEntity.getCustomerNo());
    customerCashRBucket.set(cashFlowEntity);
  }

  @DistributedLock(defaultPrefix = BusinessRedisCacheConsts.CUSTOMER_LOCK_PREFIX_KEY, expression = "#context.customerNo",
      expressionType = ExpressionTypeEnum.SPEL_EXPRESSION)
  @Override
  public CashFlowEntity saveByMemory(CashFlowMessageDTO param) {
    String customerCashKey = BusinessRedisCacheConsts.CUSTOMER_CASH_LOCK_PREFIX_KEY + param.getCustomerNo();
    RBucket<CashFlowEntity> customerCashBucket = redissonClient.getBucket(customerCashKey);

    // 从内存加载最新内存数据
    if(Objects.isNull(customerCashBucket.get())){
      loadMemoryByCustomer(param.getCustomerNo());
    }
    CashFlowEntity cacheCashFlow = customerCashBucket.get();

    // 更新数据
    cacheCashFlow.setSerialNo(cacheCashFlow.getSerialNo() + 1)
        .setCustomerNo(param.getCustomerNo())
        .setAmount(param.getAmount())
        .setTotalAmount(BigDecimalUtil.add(cacheCashFlow.getTotalAmount(), param.getTotalAmount()));
    cacheCashFlow = cashFlowService.save(cacheCashFlow);

    // 数据保存到内存中
    saveMemoryByCustomer(cacheCashFlow);
    return cacheCashFlow;
  }

  private RBucket<CashFlowEntity> getCustomerCashRBucket(String customerNo){
    String customerCashKey = BusinessRedisCacheConsts.CUSTOMER_CASH_LOCK_PREFIX_KEY + customerNo;
    RBucket<CashFlowEntity> customerCashBucket = redissonClient.getBucket(customerCashKey);
    return customerCashBucket;
  }

}
